#include <vector>
#include <algorithm>

#include <iomux/iomuxhelper.hpp>

static bool poll_read(int events)
{
    return (events & POLLIN) || (events & POLLHUP);
}

static bool poll_write(int events)
{
    return (events & POLLOUT);
}

bool PollHelper::add_file(FileDesc *f, short int events)
{
    if (m_pfds.size() == sysconf(_SC_OPEN_MAX)) {
        throw std::runtime_error("Too many file descriptors");
    }

    int fd = f->get_fd();

    struct pollfd pfd = {
        .fd = fd,
        .events = events,
        .revents = 0,
    };

    m_pfds.push_back(pfd);
    this->add_file_desc(fd, f);

    return true;
}

bool PollHelper::remove_file(int fd)
{
    for (auto it = m_pfds.begin(); it != m_pfds.end(); ++it) {
        if (it->fd == fd) {
            m_pfds.erase(it);
            this->remove_file_desc_from_fd(fd);
            return true;
        }
    }

    return false;
}

int PollHelper::poll(int timeout)
{
    int ret = ::poll(m_pfds.data(), m_pfds.size(), timeout);

    return ret;
}

bool SelectHelper::add_file(FileDesc *f, bool read, bool write, bool except)
{
    int fd = f->get_fd();

    if (fd >= FD_SETSIZE) {
        throw std::runtime_error("fd is too big");
    }

    if (read) {
        FD_SET(fd, &m_readfds);
    }

    if (write) {
        FD_SET(fd, &m_writefds);
    }

    if (except) {
        FD_SET(fd, &m_exceptfds);
    }

    if (fd > m_fd_max) {
        m_fd_max = fd;
    }

    this->add_file_desc(fd, f);
    m_fds.push_back(fd);

    return true;
}

bool SelectHelper::remove_file(int fd) {
    try {
        this->remove_file_desc_from_fd(fd);
        auto it = std::find(m_fds.begin(), m_fds.end(), fd);
        if (it != m_fds.end()) {
            m_fds.erase(it);
        }
        if (fd == m_fd_max) {
            m_fd_max = 0;
            for (auto it = m_file_map.rbegin(); it != m_file_map.rend(); ++it) {
                if (it->first > m_fd_max)
                    m_fd_max = it->first;
            }
        }
        return true;
    } catch (const std::exception &e) {
        return false;
    }
}

int SelectHelper::select(struct timeval *tv)
{
    int ret = ::select(m_fd_max + 1,
                        &m_readfds, &m_writefds, &m_exceptfds,
                        tv);

    return ret;
}

bool EpollHelper::add_epoll(FileDesc *f, const struct epoll_event *event)
{
    int fd = f->get_fd();
    int ret = ::epoll_ctl(m_epollfd, EPOLL_CTL_ADD,
                    fd, (struct epoll_event *)event);
    if (ret < 0) {
        return false;
    }

    this->add_file_desc(fd, f);

    return true;
}

bool EpollHelper::modify_epoll(FileDesc *f, const struct epoll_event *event)
{
    int fd = f->get_fd();
    int ret = ::epoll_ctl(m_epollfd, EPOLL_CTL_MOD,
                    fd, (struct epoll_event *)event);
    if (ret < 0) {
        return false;
    }

    return true;
}

bool EpollHelper::remove_epoll(FileDesc *f, const struct epoll_event *event)
{
    int fd = f->get_fd();

    int ret = ::epoll_ctl(m_epollfd, EPOLL_CTL_DEL,
                    fd, NULL);
    if (ret < 0) {
        return false;
    }

    this->remove_file_desc_from_fd(fd);

    return true;
}

int EpollHelper::wait(std::vector<struct epoll_event> &events, int timeout)
{
    struct epoll_event evs[m_file_map.size()];
    int num_events = ::epoll_wait(m_epollfd, evs, m_file_map.size(), timeout);

    if (num_events > 0) {
        events.clear();
        events.assign(evs, evs + num_events);
    }

    return num_events;
}

